#include "common.h"

#define BUFSIZE_MAX 1024

int g_connecting = CONNECTING; // 正在连接主机。。。 
int g_searching  = SEARCHING;  // 正在查找主机IP。。。

sem_t s;
char *filename;

void *searching_host(void *arg)
{
	pthread_detach(pthread_self());

	fprintf(stderr, "正在寻找主机");
	while(g_searching == SEARCHING)
	{
		fprintf(stderr, ".");
		usleep(100*1000);
	}
	printf("%s", g_searching==SEARCH_SUCCESS ? "[OK]\n": "[FAIL]\n");
	sem_post(&s);

	pthread_exit(NULL);
}

// arg: 链接
void arg_parser(char *arg, char **host, char **file)
{
	assert(arg);
	assert(host);
	assert(file);

	if(arg[strlen(arg)-1] == '/')
	{
		fprintf(stderr, "非法链接.\n");
		exit(0);
	}

	char *h, *f;
	h = f = arg;

	char *delim1 = "http://";
	char *delim2 = "https://";
	if(strstr(arg, delim1) != NULL)
	{
		h += strlen(delim1);
	}
	else if(strstr(arg, delim2) != NULL)
	{
		h += strlen(delim2);
	}

	f = strstr(h, "/");
	if(f == NULL)
	{
		fprintf(stderr, "非法链接.\n");
		exit(0);
	}
	f += 1;

	*host = calloc(1, 256);
	*file = calloc(1, 2048);

	memcpy(*host, h, f-h-1);
	memcpy(*file, f, strlen(f));
}



void progress(long long nread, long long filesize)
{
	struct winsize ws;
	ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);

	int bar_len = ws.ws_col-32;
	bar_len = bar_len > 60 ? 60 : bar_len;

	int rate = filesize/bar_len;
	int cur  = nread/rate;

	char *total = calloc(1, 16);
	if(filesize < 1024)
		snprintf(total, 16, "%llu", filesize);
	else if(filesize >= 1024 && filesize < 1024*1024)
		snprintf(total, 16, "%.1fKB", (float)filesize/1024);
	else if(filesize >= 1024*1024 && filesize < 1024*1024*1024)
		snprintf(total, 16, "%.1fMB", (float)filesize/(1024*1024));


	char *bar = calloc(1, 128);
	if(nread < 1024)
		snprintf(bar, 128, "\r[%llu/%s] [", nread, total);
	else if(nread < 1024*1024)
		snprintf(bar, 128, "\r[%.1fKB/%s] [", (float)nread/1024, total);
	else if(nread < 1024*1024*1024)
		snprintf(bar, 128, "\r[%.1fMB/%s] [", (float)nread/(1024*1024), total);
	free(total);

	int i;
	for(i=0; i<cur; i++)
		snprintf(bar+strlen(bar), 128-strlen(bar)-i, "%s", "#");

	for(i=0; i<bar_len-cur-1; i++)
		snprintf(bar+strlen(bar), 128-strlen(bar)-i, "%s", "-");

	snprintf(bar+strlen(bar), 128-strlen(bar),
			"] [%.1f%%]%c", (float)nread/filesize*100,
			nread==filesize?'\n':' ');
	fprintf(stderr, "%s", bar);
	free(bar);
}



void remote_info(struct hostent *he)
{
	assert(he);

	printf("主机的官方名称：%s\n", he->h_name);

	int i;
	for(i=0; he->h_aliases[i]!=NULL; i++)
	{
		printf("别名[%d]：%s\n", i+1, he->h_aliases[i]);
	}

	printf("IP地址长度：%d\n", he->h_length);

	for(i=0; he->h_addr_list[i]!=NULL; i++)
	{
		printf("IP地址[%d]：%s\n", i+1,
			inet_ntoa(*((struct in_addr **)he->h_addr_list)[i]));
	}
}


void *connecting_host(void *arg)
{
	pthread_detach(pthread_self());

	fprintf(stderr, "正在连接远端服务器");
	while(g_connecting == CONNECTING)
	{
		fprintf(stderr, ".");
		usleep(100*1000);
	}
	printf("%s", g_connecting==CONNECT_SUCCESS ? "[OK]\n": "[FAIL]\n");
	sem_post(&s);

	pthread_exit(NULL);
}


int main(int argc, char const *argv[])
{
	printf("请输入下载链接[直接按回车下载鸿蒙1.0源码]：\n");
    char *link = calloc(1, 1024);
    fgets(link, 1024, stdin);

    if(link[0] == '\n')
        strcpy(link, "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.10.8.tar.xz");
    else
        strtok(link, "\n");

    
    char *host;
    char *filepath;
    arg_parser(link, &host, &filepath);	//获取域名和下载地址
    free(link);

    sem_init(&s, 0, 0);
    pthread_t t;
    pthread_create(&t, NULL, searching_host, NULL);

    // 取得指定域名的IP
    struct hostent *he = gethostbyname(host);    
    if(he == NULL && errno == HOST_NOT_FOUND)
    {
        g_searching = SEARCH_FAIL;
        sem_wait(&s); // 等待进度线程输出所有信息之后，再输出下面的信息
        printf("主机名有误，或DNS没配置好.\n");
        exit(0);
    }
    else if(he == NULL)
    {
        g_searching = SEARCH_FAIL;
        sem_wait(&s); // 等待进度线程输出所有信息之后，再输出下面的信息
        printf("非法链接.\n");
        exit(0);
    }
    g_searching = SEARCH_SUCCESS;
    sem_wait(&s);

#ifdef DEBUG
	printf("host: %s\n", host);
	printf("filepath: %s\n", filepath);

	remote_info(he);
#endif

    // 从 gethostbyname() 获取的服务器的IP地址集合
    struct in_addr **addr_list = (struct in_addr **)(he->h_addr_list);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in srvaddr;
    socklen_t len = sizeof(srvaddr);
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_addr = *addr_list[0];
    srvaddr.sin_port = htons(80);

    // 将sockfd设置为非阻塞
    // 以便于可以监控到三次握手无法立即完成的状态
    long state = fcntl(sockfd, F_GETFL);
	state |= O_NONBLOCK;
	fcntl(sockfd, F_SETFL, state);

    // 搞一条线程来显示连接的进度
    pthread_create(&t, NULL, connecting_host, NULL);

    sleep(1);
    if(connect(sockfd, (struct sockaddr *)&srvaddr, len) == -1)// && errno == EINPROGRESS)
    {
        fd_set wset;
        FD_ZERO(&wset);
        FD_SET(sockfd, &wset);

        struct timeval tv = {10, 0};

        long state = fcntl(sockfd, F_GETFL);
		state &= ~O_NONBLOCK;
		fcntl(sockfd, F_SETFL, state);

        // n代表select()成功等到的已就绪的套接字的个数
        int n = select(sockfd+1, NULL, &wset, NULL, &tv);
        if(n < 0)
        {
            perror("select() failed");
            exit(0);
        }

        if(n == 0)
        {
            g_connecting = CONNECT_FAIL;
            sem_wait(&s);
            printf("链接超时\n");
            exit(0);
        }
        
        // 判定sockfd就绪
        if(FD_ISSET(sockfd, &wset))
        {
			if(connect(sockfd, (struct sockaddr *)&srvaddr, len) == 0)
			{
				g_connecting = CONNECT_SUCCESS;
			}
			else
			{
				perror("connect error");
				exit(0);
			} 
        }
    }
    g_connecting = CONNECT_SUCCESS;

    FILE *fp;
    long long curlen = 0LL;

    if(strstr(filepath, "/"))
        filename = strrchr(filepath, '/')+1;
    else
        filename = filepath;

    if(access(filename, F_OK))
    {
        fp = fopen(filename, "w"); // 新建要现在的文件
    }
    else
    {
        fp = fopen(filename, "a"); // 断点续传，追加文件内容

        struct stat fileinfo;
        stat(filename, &fileinfo);
        curlen = fileinfo.st_size;
    }

    if(fp == NULL)
	{
		perror("fopen() failed");
		exit(0);
	}

    // 设置普通文件为不缓冲状态，防止突然断网程序退出导致数据丢失
    setvbuf(fp, NULL, _IONBF, 0);

    // 发送HTTP请求报文
	char *sndbuf = calloc(1, 1024);
	http_request(sndbuf, 1024, filepath, host, curlen); 

    int n = send(sockfd, sndbuf, strlen(sndbuf), 0);
    if(n == -1)
	{
		perror("send() failed");
		exit(0);
	}
    free(sndbuf);

    long long total_bytes = curlen;


    // 收到并解析HTTP响应报文
	char *httphead = calloc(1, 2048);
	n = 0;
	while(1)
	{
		read(sockfd, httphead+n, 1);
		n++;
		if(strstr(httphead, "\r\n\r\n"))
			break;
	};
    printf("HTTP响应头部：\n%s", httphead);


    // 接收文件内容，并妥善保存起来
    long long size; // 要下载的文件的总大小
	long long len2; // 本次要下载的大小
	size = get_size(httphead);
	len2 = get_len(httphead);
    printf("文件的总大小：%lld\n", size);
    printf("本次要下载的大小：%lld\n", len2);



    // 检查HTTP响应报文
	// 返回：真：服务器正常返回
	//       假：发生错误
	if(!check_response(httphead))
	{
		// 文件已经下载
		if(curlen == size && curlen != 0)
		{
			fprintf(stderr, "该文件已下载.\n");
		}

		// 其他错误
		if(!access(filename, F_OK) && curlen == 0)
			remove(filename);

		exit(0);
	}
    free(httphead);


    // 真正接收文件内容
    int bufsize = size>BUFSIZE_MAX ? BUFSIZE_MAX : size;
    char *buf = calloc(1, bufsize);

    while(1)
    {
        int n = read(sockfd, buf, bufsize);

        if(n == 0)
            break;

        if(n == -1)
        {
            perror("读取服务器资源失败");
            break;
        }

        fwrite(buf, 1, n, fp);

        total_bytes += n ;
        progress(total_bytes, size);

        if(total_bytes >= size)
        {
            printf("[Done].\n");
            break;
        }
    }


    fclose(fp);
    free(buf);


    pthread_exit(NULL);
}
